/*
 * Socket.h
 *
 */

#ifndef SOCKET_H_
#define SOCKET_H_

#include <string>
#include <exception>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "Mutex.h"

class SocketException : public std::exception {
	public:
		explicit SocketException(const std::string& msg) throw();
		virtual ~SocketException() throw() {}
		virtual const char* what() const throw();
	private:
		std::string msg;
};

typedef unsigned short int Port;

class Socket {
	private:
		Socket(const Socket&);  // prohibido
		Socket& operator=(const Socket&);  // prohibido

		/**
		 * usado por la propia clase. No se puede obtener un socket descriptor
		 * a no ser que se hayan usado las funciones de socket.h, lo cual solo
		 * se hace en esta clase
		 */
		friend class ServerSocket;
		explicit Socket(int sock);
		Mutex sock_mutex;  // Para sincronizar el cierre del socket
	protected:
		int sock;
		Socket();
	public:
		virtual ~Socket();
		/**
		 * Recibe datos a traves del socket. Devuelve la cantidad de bytes
		 * recibida, la cual puede ser diferente al tamanio pasado como
		 * parametro. Metodo bloqueante. En caso de error lanza una excepcion
		 * SocketException
		 */
		int recv(void* data, int size);

		/**
		 * Envia datos a traves del socket. Devuelve la cantidad de bytes
		 * enviada, la cual puede ser diferente al tamanio pasado como
		 * parametro. Metodo bloqueante. En caso de error lanza una excepcion
		 * SocketException
		 */
		int send(const void* data, int size);

		/**
		 * Devuelve la direccion del socket
		 */
		std::string getIP();

		/**
		 * Cierra la recepcion y envio de datos en forma ordenada.
		 *
		 */
		void shutdown();

		/**
		 * Cierra el socket.
		 *
		 */
		void close();
};

class ServerSocket : public Socket {
	public:
		ServerSocket(Port port, int queue = 5);
		ServerSocket(const std::string& host, Port port,
					int queue = 5);
		virtual ~ServerSocket() {}

		/**
		 * Devuelve un socket conectado al nuestro. En caso de error lanza una
		 * excepcion SocketException. Metodo bloqueante.
		 */
		Socket* accept();

		void unbind();
	protected:
		/**
		 * Establece la cantidad de conexiones en cola. En caso de error lanza
		 * una excepcion SocketException
		 */
		void listen(int queue);

		/**
		 * Enlaza el socket al puerto local especificado. En caso de error
		 * lanza una excepcion SocketException
		 */
		void bind(Port port);

		/**
		 * Enlaza el socket a la direccion y puerto local especificados.
		 * En caso de error lanza una excepcion SocketException
		 */
		void bind(const std::string& host, Port port);
};

class ClientSocket : public Socket {
	public:
		ClientSocket(const std::string& host, Port port);
		virtual ~ClientSocket() {}
	protected:
		/**
		 * Conecta el socket a la direccion y puerto pasados por parametro.
		 * En caso de error lanza una excepcion SocketException
		 */
		void connect(const std::string& host, Port port);
};

#endif  /* SOCKET_H_ */
